/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package managedbean.UserRoleManagement;

import com.sun.jersey.core.util.Base64;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.validator.ValidatorException;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import merlion.common.entity.Staff;
import merlion.common.entity.SystemUserAccount;
import merlion.common.session.stateless.SystemUserSessionBeanLocal;
import merlion.common.util.Consts;
import merlion.common.util.CryptographicHelper;
import merlion.common.util.exception.NotExistException;
import merlion.common.util.exception.UpdateFailureException;
import merlion.common.util.exception.EntityExistException;
import org.primefaces.event.FlowEvent;
import java.util.ArrayList;
import javax.faces.bean.ManagedProperty;
import managedbean.LoginAdmin.LoginManagerBean;
import managedbean.Workspace.WorkspaceManagerBean;
import merlion.common.entity.SecurityRole;
import org.primefaces.event.SelectEvent;

/**
 *
 * @author Zhang Ying
 */
@ManagedBean
@ViewScoped
public class AccountManagerBean {

    @ManagedProperty(value = "#{workspaceManagerBean}")
    private WorkspaceManagerBean workspaceManagerBean;
    @EJB
    SystemUserSessionBeanLocal accountManager;
    private Staff newStaff;
    private Collection<SecurityRole> allRoles;
    private SecurityRole[] selectedRoles;
    private String confirmedPwd;
    private Collection<SystemUserAccount> allAccounts;
    // for manage accounts
    private SystemUserAccount selectedAccount;
    // for manage account filtering
    private String roleNameFilter;
    private Collection<SystemUserAccount> filteredAccounts;
    // paths defined
    private String usermgmtPath = "/UserRoleManagement/";
    // for city and country auto complete
    List<String> cityLists = new ArrayList<String>();
    List<String> countryLists = new ArrayList<String>();

    /** Creates a new instance of AccountManagerBean */
    public AccountManagerBean() {
        newStaff = new Staff();
        cityLists.add("Singapore");
        cityLists.add("Chengdu");
        cityLists.add("Hong Kong");
        countryLists.add("Singapore");
        countryLists.add("China");
    }

    private void resetCity() {
        cityLists = new ArrayList<String>();
        cityLists.add("Singapore");
        cityLists.add("Chengdu");
        cityLists.add("Hong Kong");
    }

    private void resetCountry() {
        countryLists = new ArrayList<String>();
        countryLists.add("Singapore");
        countryLists.add("China");
    }

    // for creation page
    public void checkUsernameExists(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String usernameInputed = (String) value;
        if (accountManager.checkUsernameExist(usernameInputed)) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Username exists", "Username exists");
            throw new ValidatorException(message);
        }
    }

    public void validateSamePassword(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        UIInput passwordField = (UIInput) context.getViewRoot().findComponent("form1:pwd");
        if (passwordField == null) {
            throw new IllegalArgumentException(String.format("Unable to find component."));
        }
        String password = (String) passwordField.getValue();
        String confirmPassword = (String) value;
        if (!confirmPassword.equals(password)) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match", "Passwords do not match!");
            throw new ValidatorException(message);
        } else {
            confirmedPwd = null;
        }
    }

    public void checkEmailExists(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String emailInputed = (String) value;
        if (accountManager.checkEmailExist(emailInputed)) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, Consts.CREATE_ACCOUNT_EMAIL_EXIST, Consts.CREATE_ACCOUNT_EMAIL_EXIST);
            throw new ValidatorException(message);
        }
    }

    public void save(ActionEvent actionEvent) {
        try {
            // default account status = disabled
            newStaff.getSystemUserAccount().setStatus(Consts.ACCOUNT_DISABLED);
            // generate password
            String generatedPwd = Long.toHexString(Double.doubleToLongBits(Math.random())).toString();
            newStaff.getSystemUserAccount().setEncryptedPassword(generatedPwd);
            accountManager.addNewStaff(newStaff);
            CryptographicHelper cryptographicHelper = new CryptographicHelper();
            String token = Base64.encode(cryptographicHelper.doMD5Hashing(newStaff.getSystemUserAccount().getUsername() + "." + generatedPwd)).toString();
            if (token != null) {
                //save the token to the database
                try {
                    accountManager.updateStaffToken(newStaff.getSystemUserAccount().getUsername(), token);
                } catch (UpdateFailureException ex) {
                    FacesContext.getCurrentInstance().addMessage(null,
                            new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", "Saving token not successful."));
                }
            }
            sendActivationEmail(newStaff.getSystemUserAccount().getUsername(), generatedPwd,
                    newStaff.getSystemUserAccount().getEmailAddress(), token);
            FacesMessage msg = new FacesMessage("Successful", "created new staff successfully and sent out activation email.");
            FacesContext.getCurrentInstance().addMessage(null, msg);
        } catch (EntityExistException ex) {
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", ex.getMessage()));
        }
    }

    public List<String> completeDepartments(String query) {
        List<String> results = new ArrayList<String>();
        // TODO: add all the departments
        results.add(Consts.ADMIN_DEP);
        results.add(Consts.TECH_DEP);
        results.add(Consts.FIN_DEP);
        results.add(Consts.SALES_DEP);
        results.add(Consts.MRP_DEP);
        return results;
    }

    public List<String> completeCountry(String query) {
        // TODO: implement more countries
        return countryLists;
    }

    public List<String> completeCity(String query) {
        // TODO: implement more cities
        return cityLists;
    }

    public void handleSelectCity(SelectEvent event) {
        Object item = event.getObject();
        String city = (String) item;
        countryLists = new ArrayList<String>();
        if (city.equals("Singapore")) {
            countryLists.add("Singapore");
        } else if (city.equals("Chengdu")) {
            countryLists.add("China");
        } else if (city.equals("Hong Kong")) {
            countryLists.add("China");
        } else {
            resetCountry();
        }
        resetCity();
        System.out.println("select city:" + city + countryLists.size());
    }

    public void handleSelectCountry(SelectEvent event) {
        Object item = event.getObject();
        String country = (String) item;
        cityLists = new ArrayList<String>();
        if (country.equals("China")) {
            cityLists.add("Chengdu");
            cityLists.add("Hong Kong");
        } else if (country.equals("Singapore")) {
            cityLists.add("Singapore");
        } else {
            resetCity();
        }
        resetCountry();
        System.out.println("select country: " + country + cityLists.size());
    }

    public String onFlowProcess(FlowEvent event) {
        String oldStep = event.getOldStep();
        String newStep = event.getNewStep();
        if (oldStep.equals("assignRole") && newStep.equals("confirm")) {
            Collection<SecurityRole> srs = new ArrayList<SecurityRole>();
            srs.addAll(Arrays.asList(selectedRoles));
            newStaff.setSecurityRoles(srs);
        }
        if (oldStep.equals("basicInfo") && newStep.equals("account")) {
            if (checkCountryNotMatchCity(newStaff.getHomeAddress().getCity(), newStaff.getHomeAddress().getCountry())) {
                FacesContext.getCurrentInstance().addMessage(null,
                        new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", "The country and city are not matched. Please check again."));
                return event.getOldStep();
            }
        }
        return event.getNewStep();
    }

    // for manage page
    public void checkUsernameExists2(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String usernameInputed = (String) value;
        if (!selectedAccount.getUsername().equals(usernameInputed)
                && accountManager.checkUsernameExist(usernameInputed)) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Username exists", "Username exists");
            throw new ValidatorException(message);
        }
    }

    public void checkEmailExists2(FacesContext context, UIComponent component, Object value) throws ValidatorException {
        String emailInputed = (String) value;
        if (!selectedAccount.getEmailAddress().equals(emailInputed)
                && accountManager.checkEmailExist(emailInputed)) {
            FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, Consts.CREATE_ACCOUNT_EMAIL_EXIST, Consts.CREATE_ACCOUNT_EMAIL_EXIST);
            throw new ValidatorException(message);
        }
    }

    public String selectRole(Long id) {
        setSelectedAccount(accountManager.getSystemUserAccount(id));
        return null;
    }

    public String initSelectedRoles(Long id) {
        setSelectedAccount(accountManager.getSystemUserAccount(id));
        getSelectedRoles();
        return null;
    }

    public String editAccountInfo(Long id) {
        setSelectedAccount(accountManager.getSystemUserAccount(id));
        return getRedirectPath("/UserRoleManagement/", "manageAccountsInfo") + "&accId=" + id;

    }

    public String resetPassword(Long id) {
        setSelectedAccount(accountManager.getSystemUserAccount(id));
        try {
            // pwd reset
            String generatedPwd = Long.toHexString(Double.doubleToLongBits(Math.random())).toString();
            selectedAccount.setEncryptedPassword(generatedPwd);
            selectedAccount.setStatus(Consts.ACCOUNT_DISABLED);
            accountManager.updateSystemUserAccount(selectedAccount);
            // new token and resend email
            CryptographicHelper cryptographicHelper = new CryptographicHelper();
            String token = Base64.encode(cryptographicHelper.doMD5Hashing(selectedAccount.getUsername() + "." + generatedPwd)).toString();
            if (token != null) {
                //save the token to the database
                try {
                    accountManager.updateStaffToken(selectedAccount.getUsername(), token);
                } catch (UpdateFailureException ex) {
                    FacesContext.getCurrentInstance().addMessage(null,
                            new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", "Saving token not successful."));
                }
            }
            sendResetEmail(selectedAccount.getUsername(), generatedPwd,
                    selectedAccount.getEmailAddress(), token);
            updateCurrentStaff();
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "Successful", "Password is reset and email sent."));
        } catch (UpdateFailureException ex) {
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", ex.getMessage()));
        }
        return null;
    }

    public void updateSecurityRoles(ActionEvent event) {
        Collection<SecurityRole> updatedSecuirtyRoles = new ArrayList<SecurityRole>();
        updatedSecuirtyRoles.addAll(Arrays.asList(selectedRoles));
        // persist to database
        try {
            if (updatedSecuirtyRoles.isEmpty()) {
                FacesContext.getCurrentInstance().addMessage(null,
                        new FacesMessage(FacesMessage.SEVERITY_ERROR, "Please select at least one role", "Please select at least one role"));
                return;
            }
            accountManager.updateSystemUserAccount(selectedAccount, updatedSecuirtyRoles);
            updateCurrentStaff();
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "Successful", "Account is updated successfully."));
        } catch (Exception ex) {
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", ex.getMessage()));
        }
    }

    public void updateEditAccount(ActionEvent event) {
        // persist to database
        try {
            accountManager.updateSystemUserAccount(selectedAccount);
            updateCurrentStaff();
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "Successful", "Account is updated successfully."));
        } catch (UpdateFailureException ex) {
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", ex.getMessage()));
        }
    }

    public void deleteSelectedAccount(ActionEvent event) {
        try {
            if (selectedAccount.getId().equals(workspaceManagerBean.getCurrentStaff().getSystemUserAccount().getId())) {
                FacesContext.getCurrentInstance().addMessage(null,
                        new FacesMessage(FacesMessage.SEVERITY_ERROR, "You cannot delete your current account", "You cannot delete your current account"));
                return;
            }
            accountManager.deleteSystemUserAccount(selectedAccount.getId());
            setAllAccounts(accountManager.getAllSystemUserAccounts());
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_INFO, "Successful", "Account is deleted successfully."));
        } catch (NotExistException ex) {
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR, "Unsuccessful", ex.getMessage()));
        } catch (Exception ex) {
            FacesContext.getCurrentInstance().addMessage(null,
                    new FacesMessage(FacesMessage.SEVERITY_ERROR,
                    "Unsuccessful",
                    "Sorry, your deletion failed. The staff may have customers assigned to him"));
        }
    }

    // for manage page advance filter
    public String getRoleNameFilter() {
        return roleNameFilter;
    }

    public void setRoleNameFilter(String roleNameFilter) {
        this.roleNameFilter = roleNameFilter;
    }

    public Collection<SystemUserAccount> getFilteredAccounts() {
        if (filteredAccounts == null) {
            HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
            roleNameFilter = request.getParameter("rolename");
            if (roleNameFilter != null) {
                filteredAccounts = accountManager.getFilteredAccounts(roleNameFilter);
            }
        }
        return filteredAccounts;
    }

    public void setFilteredAccounts(Collection<SystemUserAccount> filteredAccounts) {
        this.filteredAccounts = filteredAccounts;
    }

    public String filterByRolename() {
        return redirectToMgAccFilter() + "&rolename=" + roleNameFilter;
    }

    public String redirectToMgAccFilter() {
        return getRedirectPath(usermgmtPath, "manageAccountsFilter");
    }

    public String redirectToMgAccs() {
        return getRedirectPath(usermgmtPath, "manageAccounts");
    }

    // setters and getters
    public SystemUserAccount getSelectedAccount() {
        if (selectedAccount == null) {
            HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
            String parameter = request.getParameter("accId");
            if (parameter != null) {
                Long id = Long.valueOf(parameter);
                selectedAccount = accountManager.getSystemUserAccount(id);
            }
        }
        return selectedAccount;
    }

    public void setSelectedAccount(SystemUserAccount selectedAccount) {
        this.selectedAccount = selectedAccount;
    }

    public Staff getNewStaff() {
        return newStaff;
    }

    public void setNewStaff(Staff newStaff) {
        this.newStaff = newStaff;
    }

    public Collection<SecurityRole> getAllRoles() {
        allRoles = accountManager.getAllSecurityRoles();
        return allRoles;
    }

    public void setAllRoles(Collection<SecurityRole> allRoles) {
        this.allRoles = allRoles;
    }

    public SecurityRole[] getSelectedRoles() {
        if (selectedAccount != null) {
            Collection<SecurityRole> srLsts = selectedAccount.getStaff().getSecurityRoles();
            SecurityRole[] srs = new SecurityRole[srLsts.size()];
            int i = 0;
            for (SecurityRole sr : srLsts) {
                srs[i] = sr;
                i++;
            }
            selectedRoles = srs;
        }
        return selectedRoles;
    }

    public void setSelectedRoles(SecurityRole[] selectedRoles) {
        this.selectedRoles = selectedRoles;
    }

    public String getConfirmedPwd() {
        return confirmedPwd;
    }

    public void setConfirmedPwd(String confirmedPwd) {
        this.confirmedPwd = confirmedPwd;
    }

    public Collection<SystemUserAccount> getAllAccounts() {
        allAccounts = accountManager.getAllSystemUserAccounts();
        return allAccounts;
    }

    public void setAllAccounts(Collection<SystemUserAccount> allAccounts) {
        this.allAccounts = allAccounts;
    }

    public String[] getAllStatus() {
        String[] result = new String[2];
        result[0] = Consts.ACCOUNT_ACTIVATED;
        result[1] = Consts.ACCOUNT_DISABLED;
        return result;
    }

    public WorkspaceManagerBean getWorkspaceManagerBean() {
        return workspaceManagerBean;
    }

    public void setWorkspaceManagerBean(WorkspaceManagerBean workspaceManagerBean) {
        this.workspaceManagerBean = workspaceManagerBean;
    }

    // private helper methods
    private String getRedirectPath(String parentPath, String actionPath) {
        return parentPath + actionPath + "?faces-redirect=true";
    }

    private String getRedirectFullPath(String parentPath, String actionPath) {
        String serverName = FacesContext.getCurrentInstance().getExternalContext().getRequestServerName();
        String serverPort = "8080";
        return "http://" + serverName + ":" + serverPort + "/merlion-war" + parentPath + actionPath + ".xhtml";
    }

    // private methods for sending email to the queue
    private void sendActivationEmail(String username, String password, String emailAddr, String token) {
        // send the email to MDB
        try {
            Context c = new InitialContext();
            ConnectionFactory cf = (ConnectionFactory) c.lookup("jms/queueSendEmailFactory");
            Connection conn = null;
            Session s = null;

            try {
                conn = cf.createConnection();
                s = conn.createSession(false, s.AUTO_ACKNOWLEDGE);
                Destination destination = (Destination) c.lookup("jms/queueSendEmail");
                MessageProducer mp = s.createProducer(destination);
                MapMessage mmsg = s.createMapMessage();
                mmsg.setString("token", token);
                mmsg.setString("username", username);
                mmsg.setString("password", password);
                mmsg.setString("email", emailAddr);
                mmsg.setString("type", Consts.ACCOUNT_ACTIVATION);

                mp.send(mmsg);
            } catch (JMSException jmsEx) {
                jmsEx.printStackTrace();;
            } finally {
                if (s != null) {
                    s.close();
                }
                if (conn != null) {
                    conn.close();
                }
            }
        } catch (NamingException namingEx) {
            namingEx.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    // private methods for sending email to the queue

    private void sendResetEmail(String username, String password, String emailAddr, String token) {
        // send the email to MDB
        try {
            Context c = new InitialContext();
            ConnectionFactory cf = (ConnectionFactory) c.lookup("jms/queueSendEmailFactory");
            Connection conn = null;
            Session s = null;

            try {
                conn = cf.createConnection();
                s = conn.createSession(false, s.AUTO_ACKNOWLEDGE);
                Destination destination = (Destination) c.lookup("jms/queueSendEmail");
                MessageProducer mp = s.createProducer(destination);
                MapMessage mmsg = s.createMapMessage();
                mmsg.setString("token", token);
                mmsg.setString("username", username);
                mmsg.setString("password", password);
                mmsg.setString("email", emailAddr);
                mmsg.setString("type", Consts.ACCOUNT_RESET);

                mp.send(mmsg);
            } catch (JMSException jmsEx) {
                jmsEx.printStackTrace();;
            } finally {
                if (s != null) {
                    s.close();
                }
                if (conn != null) {
                    conn.close();
                }
            }
        } catch (NamingException namingEx) {
            namingEx.printStackTrace();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    // update current staff
    private void updateCurrentStaff() {
        Staff s = accountManager.getStaff(workspaceManagerBean.getCurrentStaff().getId());
        workspaceManagerBean.setCurrentStaff(s);
        workspaceManagerBean.setLoggedUsername(s.getSystemUserAccount().getUsername());
        FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put(LoginManagerBean.AUTH_KEY, s);
    }

    private boolean checkCountryNotMatchCity(String city, String country) {
        if (country.equals("China")){
            if (!city.equals("Chengdu") && !city.equals("Hong Kong")){
                return true;
            }
        } else if (country.equals("Singapore")){
            if (!city.equals("Singapore")){
                return true;
            }
        }else {
            return true;
        }
        return false;
    }
}
